#define _WIN32_WINNT 0x500
#include "Win32dow.hpp"
#include <windowsx.h>
#include <stdexcept>

using namespace std;

namespace Win32dow
{
	BOOL OnCreate( HWND hwnd, LPCREATESTRUCT lpCreateStruct )
	{
		Window *window= (Window*)lpCreateStruct->lpCreateParams;
		SetWindowLongPtr( hwnd, 0, (LONG_PTR)window );
		string name= typeid( *window ).name();
		return true;
	}

	void OnPaint( HWND hwnd )
	{
		Window* window= (Window*)GetWindowLongPtr( hwnd, 0 );

		RECT client;
		GetClientRect( hwnd, &client );

		PAINTSTRUCT ps;
		HDC screenHdc= BeginPaint( hwnd, &ps );

		HDC hdc= CreateCompatibleDC( screenHdc );
		HBITMAP bitmap= CreateCompatibleBitmap( screenHdc, client.right + 1, client.bottom + 1 );
		HGDIOBJ oldBitmap= SelectObject( hdc, bitmap );
	
		BitBlt( hdc, 0, 0, client.right + 1, client.bottom + 1,
				screenHdc, 0, 0, SRCCOPY );

		SelectObject( hdc, GetStockObject( DC_PEN ) );
		SelectObject( hdc, GetStockObject( DC_BRUSH ) );

		Canvas canvas( hdc );

		window->paint( canvas );

		BitBlt( screenHdc, 0, 0, client.right + 1, client.bottom + 1,
				hdc, 0, 0, SRCCOPY );

		EndPaint( hwnd, &ps );

		SelectObject( hdc, oldBitmap );
		DeleteObject( bitmap );
		DeleteDC( hdc );
	}

	void OnLButtonDown( HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags )
	{
		Window* window= (Window*)GetWindowLongPtr( hwnd, 0 );

		window->mouseButtonDown( x, y );

		SetCapture( hwnd );
	}

	void OnMouseMove( HWND hwnd, int x, int y, UINT keyFlags )
	{
		Window* window= (Window*)GetWindowLongPtr( hwnd, 0 );

		window->mouseMove( x, y );
	}

	void OnLButtonUp( HWND hwnd, int x, int y, UINT keyFlags )
	{
		Window* window= (Window*)GetWindowLongPtr( hwnd, 0 );

		window->mouseButtonUp( x, y );

		ReleaseCapture();
	}

	void OnMouseWheel( HWND hwnd, int xPos, int yPos, int zDelta, UINT fwKeys )
	{
		Window* window= (Window*)GetWindowLongPtr( hwnd, 0 );

		POINT p= {xPos, yPos};

		ScreenToClient( hwnd, &p );

		window->mouseWheel( p.x, p.y, zDelta > 0 );
	}

	void OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify )
	{
		Window* window= (Window*)GetWindowLongPtr( hwnd, 0 );

		if (hwndCtl == 0 && codeNotify == 0)
		{
			window->menuSelect( id );
		}
	}

	void OnDestroy( HWND hwnd )
	{
		PostQuitMessage( 0 );
	}

	LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
	{
		switch (message)
		{
			HANDLE_MSG( hwnd, WM_CREATE, OnCreate );
			HANDLE_MSG( hwnd, WM_PAINT, OnPaint );
			HANDLE_MSG( hwnd, WM_LBUTTONDOWN, OnLButtonDown );
			HANDLE_MSG( hwnd, WM_MOUSEMOVE, OnMouseMove );
			HANDLE_MSG( hwnd, WM_LBUTTONUP, OnLButtonUp );
			HANDLE_MSG( hwnd, WM_MOUSEWHEEL, OnMouseWheel );
			HANDLE_MSG( hwnd, WM_COMMAND, OnCommand );
			HANDLE_MSG( hwnd, WM_DESTROY, OnDestroy );
		}

		return DefWindowProc( hwnd, message, wParam, lParam );
	}

	void Register( HINSTANCE hInstance, const char* className )
	{
		WNDCLASS wndclass ;
		memset( &wndclass, 0, sizeof( WNDCLASS ) );

		if (hInstance == 0)
			throw logic_error( "Cannot create windows until you initialized hInstance" );

		if (GetClassInfo( hInstance, className, &wndclass ) == 0)
		{
			wndclass.style         = CS_HREDRAW | CS_VREDRAW;
			wndclass.lpfnWndProc   = WndProc;
			wndclass.cbClsExtra    = 0;
			wndclass.cbWndExtra    = sizeof( Window* );
			wndclass.hInstance     = hInstance;
			wndclass.hIcon         = NULL;
			wndclass.hCursor       = LoadCursor( NULL, IDC_ARROW );
			wndclass.hbrBackground = NULL;
			wndclass.lpszMenuName  = NULL;
			wndclass.lpszClassName = className;

			if (RegisterClass( &wndclass ) == 0)
				throw runtime_error( string("Unable to register class ") + className );
		}
	}
}